defmodule Scheduler do
	@moduledoc """
	    任务调度模块
	"""
	require Logger

	@doc """
	24 小时制
	[
	# 每周星期几的几点几分执行 mfa
	{:every_week, weekday, hour, min, m, f, a},
	# 每天的几点几分执行 mfa
	{:every_day, hour, min, m, f, a},
	# 每个月的某天某时某分执行 mfa, day <= 31, 如果 day 大于当月的最大天数,
	    则按最大天数计算. 比如, 如果4月, day= 31, 当月最大天数为30天, 则在4月30日执行mfa
	{:every_month, day, hour, min, m, f, a}
	]
	example:
	[
	    {:every_week, 1, 13, 20, m, f, a},
	    {:every_day, 15, 40, m, f, a},
	    {:every_month, 28, 16, 40, m, f, a}
	]
	"""
	def start(jobs) do
		jobs_valid!(jobs)
		Enum.each(
			jobs,
			fn
				({:every_week, weekday, hour, min, m, f, a}) ->
					now = TimeLib.now()
					now_weekday = Timex.weekday!(now)
					apply_ts = if now_weekday < weekday || (
						now_weekday == weekday && (now.hour < hour || (now.hour == hour && now.minute < min))
						) do
						%{
							NaiveDateTime.add(now, (weekday - now_weekday) * 86400) |
							hour: hour,
							minute: min,
							second: 0
						}
					else
						%{
							NaiveDateTime.add(now, (7 - now_weekday + weekday) * 86400) |
							hour: hour,
							minute: min,
							second: 0
						}
					end
					diff_sec = NaiveDateTime.diff(apply_ts, now)
					:timer.apply_after(diff_sec * 1000, __MODULE__, :run, [:every_week, m, f, a])
				({:every_day, hour, min, m, f, a}) ->
					now = TimeLib.now()
					apply_ts = if now.hour < hour || (now.hour == hour && now.minute < min) do
						%{now | hour: hour, minute: min, second: 0}
					else
						%{NaiveDateTime.add(now, 86400) | hour: hour, minute: min, second: 0}
					end
					diff_sec = NaiveDateTime.diff(apply_ts, now)
					:timer.apply_after(diff_sec * 1000, __MODULE__, :run, [:every_day, m, f, a])
				({:every_month, day, hour, min, m, f, a}) ->
					now = TimeLib.now()
					days = Timex.days_in_month(now)
					next_month = NaiveDateTime.add(%{now | day: 1}, days * 86400)
					next_month_days = Timex.days_in_month(next_month)
					is_last_day? = day > next_month_days
					apply_ts = if !is_last_day? && now.day < day || (
						now.day == day && (now.hour < hour || now.minute < min)) do
						%{now | day: day, hour: hour, minute: min, second: 0}
					else
						day = if is_last_day? do
							next_month_days
						else
							day
						end
						%{
							NaiveDateTime.add(now, (days - now.day + day) * 86400) |
							day: day,
							hour: hour,
							minute: min,
							second: 0
						}
					end
					diff_sec = NaiveDateTime.diff(apply_ts, now)
					:timer.apply_after(diff_sec * 1000, __MODULE__, :run, [:every_month, day, m, f, a])
			end
		)
	end

	def run(:every_week, m, f, a) do
		Logger.debug("Run EveryWeek Job, #{inspect({m, f, a})}")
		try do
			apply(m, f, a)
			now = TimeLib.now()
			next = NaiveDateTime.add(now, 86400 * 7)
			(NaiveDateTime.diff(next, now) * 1000)
			|> :timer.apply_after(m, f, a)
		rescue
			reason ->
				"Run EveryMonth Job Error, #{inspect({m, f, a})},reason: #{inspect(reason)},"
				<> "stacktrace: #{inspect(__STACKTRACE__)}"
				|> Logger.error()
				:timer.apply_after(10_000, m, f, a)
		catch
			error ->
				"Run EveryMonth Job Error, #{inspect({m, f, a})},reason: #{inspect(error)},"
				<> "stacktrace: #{inspect(__STACKTRACE__)}"
				|> Logger.error()
				:timer.apply_after(10_000, m, f, a)
		end

	end
	def run(:every_day, m, f, a) do
		Logger.debug("Run EveryDay Job, #{inspect({m, f, a})}")
		try do
			apply(m, f, a)
			now = TimeLib.now()
			next = NaiveDateTime.add(now, 86400)
			:timer.apply_after(abs(NaiveDateTime.diff(next, now)) * 1000, m, f, a)
		rescue
			reason ->
				"Run EveryDay Job Error, #{inspect({m, f, a})},reason: #{inspect(reason)},"
				<> "stacktrace: #{inspect(__STACKTRACE__)}"
				|> Logger.error()
				:timer.apply_after(10_000, m, f, a)
		catch
			error ->
				"Run EveryDay Job Error, #{inspect({m, f, a})},reason: #{inspect(error)},"
				<> "stacktrace: #{inspect(__STACKTRACE__)}"
				|> Logger.error()
				:timer.apply_after(10_000, m, f, a)
		end

	end
	def run(:every_month, day1, m, f, a) do
		Logger.debug("Run EveryMonth Job, #{inspect({m, f, a})}")
		try do
			apply(m, f, a)
			now = TimeLib.now()
			days = Timex.days_in_month(now)
			next_month = NaiveDateTime.add(%{now | day: 1}, days * 86400)
			next_month_days = Timex.days_in_month(next_month)
			day = if day1 > next_month_days do
				next_month_days
			else
				day1
			end
			NaiveDateTime.add(now, (days - now.day + day) * 86400)
			|> :timer.apply_after(__MODULE__, :run, [:every_month, day1, m, f, a])
		rescue
			reason ->
				"Run EveryMonth Job Error, #{inspect({m, f, a})},reason: #{inspect(reason)},"
				<> "stacktrace: #{inspect(__STACKTRACE__)}"
				|> Logger.error()
				:timer.apply_after(10_000, m, f, a)
		catch
			error ->
				"Run EveryMonth Job Error, #{inspect({m, f, a})},reason: #{inspect(error)},"
				<> "stacktrace: #{inspect(__STACKTRACE__)}"
				|> Logger.error()
				:timer.apply_after(10_000, m, f, a)
		end
	end

	defp jobs_valid!(jobs) do
		Enum.each(
			jobs,
			fn
				({:every_day, hour, min, _m, _f, _a}) ->
					hour_valid?(hour) && minute_valid?(min) || throw({:error, :every_day, hour, min, "错误"})
				({:every_month, day, hour, min, _m, _f, _a}) ->
					day >= 1 && day <= 31 && hour_valid?(hour) && minute_valid?(min) ||
						throw({:error, :every_month, day, hour, min, "错误"})
				({:every_week, weekday, hour, min, _m, _f, _a}) ->
					weekday >= 1 && weekday <= 7 && hour_valid?(hour) && minute_valid?(min) ||
						throw({:error, :every_week, weekday, hour, min, "错误"})
			end
		)
	end


	defp hour_valid?(hour) do
		hour >= 0 && hour <= 24
	end

	defp minute_valid?(min) do
		min >= 0 && min <= 59
	end

end
